Tutti SSD-Backed KV Cache
一句话结论:Tutti 让 SSD-backed KV Cache 变得实用,靠的不是把 SSD 伪装成慢内存,而是让存储 I/O 成为 GPU 执行计划的一部分。
SSD 慢在带宽之外
长上下文 Prefix Cache 有一个看似简单的收益公式。若某段前缀已经计算过,只要恢复已有 KV 的时间加上剩余后缀的 Prefill 时间,小于从头计算完整 Prompt 的时间,复用就值得:
$$
T_{lookup}+T_{restore}+T_{suffix}<T_{\mathrm{full\ prefill}}
$$
问题在 $T_{restore}$。vLLM、SGLang 一类引擎用 Paged KV Cache 把变长序列拆成不连续小块,便于显存分配、共享与回收;到了 SSD,这种显存布局会直接变成 I/O 碎片。
Tutti 论文 §2.2 给了一个很有冲击力的例子。对 64 层 Qwen3-32B,若每个 Block 保存 64 个 Token,那么恢复 128K Token 的 K/V 需要处理:
$$
N_{object}=2\times L\times \frac{N}{B}
=2\times64\times\frac{128\times1024}{64}
=262{,}144
$$
这里 2 是 Key 与 Value,$L$ 是层数,$N$ 是命中 Token 数,$B$ 是每个 Block 的 Token 数。论文给出的单对象大小约为 80 KB。一次逻辑上连续的前缀恢复,物理上变成约 25.6 万个散落对象。
传统 HBM—DRAM—SSD 路径还叠加了三类开销:
- 数据路径开销:普通 SSD 路径先把数据读到 Host DRAM,再复制到 HBM。
- 控制路径开销:CPU 为大量小 I/O 准备描述符、提交请求、处理完成并同步 GPU。
- 流水碎片开销:按层搬运本可与计算重叠,却会进一步缩小单次 I/O 粒度、增加提交次数。
GDS 只解决了第一项。NVIDIA 官方说明明确区分了直接 DMA 数据路径与运行在 CPU 上的驱动控制路径:NVMe 可以直接读写 GPU Memory,但 cuFile 调用和文件系统、驱动仍由 Host 发起。因此 Tutti 论文观察到,LMCache-GDS 虽然绕过 bounce buffer,GPU bubble 仍超过总推理时延的 70%。
Tutti 换掉了什么
先看论文最重要的前后对比。左侧 LMCache 的普通路径需要 DRAM 中转;GDS 去掉数据中转后,CPU 仍逐次 initiate I/O。右侧 Tutti 中,CPU 只按层准备和加载 I/O Kernel,GPU 批量发起对象读写,SSD 与 HBM 直接交换数据。
我更愿意把 Tutti 称为 CPU-prepared、GPU-executed 的 KV 对象存储。这个说法比“GPU 完全接管存储”更准确:
- CPU 仍然保留 Prefix Hash、逻辑 Block 到 GPU File 的映射、分配与全局共享等不适合 SIMT 的元数据工作。
- GPU 接管的是运行时关键路径:根据预先准备的 I/O Context 并行生成 NVMe 命令、提交请求、观察完成,并把数据直接搬到目标 HBM Block。
- CPU 开销从逐层逐块变成逐层:论文将关键路径复杂度从 $O(L\times B)$ 降到 $O(L)$,其中 $B$ 是本次命中的 KV Block 数。
这三项设计并不是同义重复:对象存储解决“读什么、放哪里”,gio_uring 解决“谁来发起和收割完成”,slack-aware Scheduler 解决“什么时候发,才不会抢走推理资源”。
从散页到 GPU KV 对象
直接把传统文件系统或 GPU Block I/O 搬进 KV Cache 并不够。LLM 引擎需要按 Prefix Block 分配和共享,却希望按 Layer 批量恢复;SSD 又需要足够大的并发请求才能吃满带宽。Tutti 在 GeminiFS 之上增加 GPU-centric KV Object Store,把这几个粒度接起来。
GPU File Pool
运行时为每个逻辑 KV Block 关联一个 GPU File。一个 GPU File 内按 Layer 保存 Key Object 与 Value Object,因此共有 $2L$ 个对象;底层 NVMe File Pool 则预先在各块 SSD 上分配物理 Extent。Tensor-Stripe Layout 保留原始 Tensor 粒度,并用轮询方式把对象分散到多块本地 NVMe,避免运行时创建文件和重新分配 Extent。
这里有一个容易混淆的边界:Block 粒度没有消失。 Prefix 仍可按 Block 命中、共享和淘汰;变化发生在传输接口上,retrieve_layer 与 store_layer 会把同一 Layer 的多个 Block 组织成一批 GPU I/O Context,再由 GPU 并行发出。
P2P 映射与 SGL
NVMe Controller 不能直接理解 CUDA Virtual Address,需要 PCIe 可见的物理地址描述符。vLLM 会在启动时预分配相对稳定的 KV Memory Pool,Tutti 因而可以提前构建 P2P Memory Mapping Table,运行时不再逐请求做地址翻译。
论文没有沿用固定 4 KB 页的 PRP List,而是选择 NVMe Scatter Gather List(SGL)。在作者给出的 60 GB KV Pool 配置中:
| 描述方式 | 表达粒度 | 论文估算的 HBM 元数据 | 作用 |
|---|---|---|---|
| PRP List | 4 KB Physical Page | 约 3.75 GB | 通用,但中等大小 KV Object 需要大量 List Page |
| SGL | 一个 16 B Descriptor 描述连续物理区间 | 约 15 MB | 更贴合约 100 KB 的 KV I/O,并减少命令描述开销 |
这两个数字依赖论文的 60 GB Pool、4 KB Page 与 64 KB 分配假设,不是所有部署的固定比例。对应的单线程 500 MB Microbenchmark 中,SGL 的读写带宽分别为 8.891 GB/s 和 2.922 GB/s,PRP 为 0.287 GB/s 和 0.032 GB/s;这是描述符路径的隔离实验,不能直接当成端到端推理加速比。
GPU io_uring 如何工作
有了对象和物理地址,GPU 还需要一种不阻塞计算的提交方式。Tutti 模仿 Linux io_uring 的 Submission/Completion 结构,实现了 GPU 侧的 gio_uring:
- SQ 与 CQ:一对 Lock-free Ring Buffer 驻留在 HBM,并通过 non-cached
mmap映射给 CPU。 - IOCB:每个 Submission Entry 是一个 I/O Control Block,内含 2048 个 IOCTX,负责成批承载细粒度请求。
- IOCTX:记录 SGL Address、GPU File Offset 与 Length,是 GPU 真正发给 NVMe 的轻量请求描述。
- CUDA Event:约束目标 HBM Block、I/O Kernel 与 Attention Kernel 的依赖,防止异步执行读到未完成数据或提前覆盖仍在使用的 Block。
一次本地命中的教学化路径如下。它概括论文 §3.1—§3.2 的对象生命周期,不是公开源码摘录:
1 | CPU Runtime |
仅使用不同 CUDA Stream 仍可能失效:长时间运行的 I/O Kernel 会占住 SM,而 GPU Kernel 调度通常不能像 CPU Thread 那样及时抢占。Tutti 用 NVIDIA Green Context 把 SM 划成 Compute Domain 与 I/O Control Domain,使 I/O Kernel 只能使用预留 SM。CUDA Programming Guide 也强调,Green Context 能限制工作使用指定 SM、减少相互干扰,但不能无条件保证并发;具体划分仍要按负载实验确定。
异步之后还要调度
把 I/O 放到 GPU 并不等于“越并行越快”。论文发现两种干扰:
- Read 与 Write 争用 SSD 内部资源。 在作者的 FIO 复现实验中,256 MB Read/Write 同时进行使总带宽下降 60%,而分开执行可以接近饱和。
- I/O 与模型 Kernel 争用 SM。 Embedding、Normalization、GEMM 等阶段可能占用接近 90% 的 GPU 资源;不受约束的 I/O Kernel 会抬高推理尾延迟。
Tutti 没有在请求到来时运行复杂优化器,而是离线 Profile 每层的空闲窗口,建立以 Input Length 与 Prefix Length 为键的 Lookup Table。表项记录可用窗口时长、SM Budget,以及不同 IOCB Count 的 I/O 执行特征。部署配置不变时,运行时只需查表。
调度优先级也很具体:
- Read 优先。 恢复历史 KV 位于 TTFT 关键路径;合适 Slack 存在时,发出能放入窗口的最大 IOCB Batch。
- 无窗口就立即读。 高命中导致剩余计算太少时,继续等只会让 Attention 更晚拿到 KV。
- Write 可以延后。 新生成 KV 的
store_layer先留在 SQ;Prefill 没有安全窗口时推迟到 Decode,甚至后续请求的 Slack 中 Best-effort Flush。 - Read/Write 解耦。 不为了表面上的 Layer-wise Overlap 同时压满双向 I/O,避免 SSD 带宽塌陷。
这是一种很务实的工程取舍:用启动前 Profile 换运行时确定性。代价也很明确——模型版本、GPU/SSD 拓扑、Kernel 实现或资源分配改变后,原表不应被默认视为仍然有效。
KV 到底留在哪里
理解 Tutti 时,最好把元数据、KV 数据和执行状态分开。它们没有一起“迁移到 GPU”:
| 对象 | 主要位置 | 谁管理 | 生命周期与移动 |
|---|---|---|---|
| Prefix Hash、Block Index | CPU Memory | vLLM / KVConnector | 请求查询与共享管理;不随 KV Payload 写入 Attention Block |
| GPU File 与 NVMe File 映射 | CPU Memory | Tutti Runtime | 启动预分配,运行时查询;CPU 保持 Engine-visible Mapping |
| P2P Mapping、SQ/CQ、IOCB | GPU HBM | Tutti gio_uring |
映射表长期保留;IOCB 随 Layer Batch 循环使用 |
| 活跃 KV Block | GPU HBM | vLLM Block Manager | Attention 前必须就绪;读取完成后由当前请求消费 |
| 本地历史 KV Object | NVMe SSD | Tutti / GeminiFS | HBM Eviction 时写入,需要复用时 P2P DMA 回 HBM |
| 集群 Replica Metadata | CPU / Mooncake Control Plane | Mooncake | 记录空间与位置,不承载正常本地 KV Payload |
单机本地路径是论文完成度最高的部分。多 GPU 时,每个 vLLM GPU Process 配一个 Tutti Instance 和独立 NVMe SQ/CQ Queue Pair,各自管理本 Rank 的 KV 分片;GPU 之间并不先聚合成一份完整 KV 再访问 SSD。
跨节点则要谨慎。论文 §3.4 用 Mooncake 负责全局空间、Replica Metadata 与 Location Lookup,但当前 Prototype 的 Remote Retrieval 仍通过 CPU Interface 把 GPU File 读到 Host Memory,再经 RDMA 发送。所以 Tutti 的强证据是 Local GPU↔Local NVMe Fast Path,不是端到端 GPU-driven Remote KV。
实验证明到哪里
截至 2026-08-26,本文使用的公开版本仍是 arXiv v1,下面讨论的是作者报告结果,不是独立复现。论文在一台 64 核 Intel Xeon 6530、512 GB Memory、两张 80 GB H100 与四块 7.68 TB Solidigm D7-PS1010 上评测;分层配置分配 256 GB Pinned DRAM,并为每张 GPU 提供 14 TB SSD Volume。端到端主实验使用单卡 Llama3-8B、LEval 与 LooGLE,并比较 vLLM 0.12.0/0.17.0 上的 HBM、LMCache-DRAM-LW、LMCache-SSD、LMCache-GDS 与 Tutti。请求按 Poisson Arrival 模拟,报告的是并发负载下的 Average TTFT/ITL。
值得抓住的不是某一个最漂亮的百分比,而是三层证据彼此闭合:
- 容量层:在论文配置中,HBM 在 LEval/LooGLE 的命中率为 8%/4%,DRAM 为 53%/24%,SSD 为 84%/86%。大容量确实减少了重算。
- 数据路径层:双 SSD RAID-0 Microbenchmark 中,Tutti Retrieve 最高达到 25.9 GB/s,LMCache-GDS 约 11.9 GB/s;Tutti Store 在长序列约 9.8 GB/s。
- 端到端层:新版 vLLM 的 LEval 高负载点上,Tutti 相比 DRAM 与 GDS 分别降低 69.1% 和 78.3% TTFT;在 1 秒 TTFT SLO 下,可承载 Request Rate 分别提高 50% 与 100%。
Slack-aware Pipeline 的边界同样重要。论文固定 32K Prompt、改变 Hit Rate 时,Tutti 的平均 Bubble 约 25 ms,在 93.75% Hit Rate 时为 6 ms,并把 compute-bound 到 I/O-bound 的 Crossover Point 推到 98.3%。但在固定 128K Input 的另一组实验中,当复用前缀超过 96K、工作负载接近纯 Retrieval 时,DRAM 重新领先,Tutti 最多落后 20.6%。
成本结论也不是 SSD 单价的同义反复。论文按 H100 5 美元/小时、DRAM 0.0088 美元/GB/小时、SSD 0.000082 美元/GB/小时计算单位 Token 成本;在 LooGLE 0.5 QPS 点,Tutti 相比 LMCache-GDS 降低约 27%。这个结果同时依赖更低存储价格与更高 GPU Utilization,换一组云价格、命中分布或写放大,比例都会变化。
什么时候值得采用
我的判断是,Tutti 最值得被看成一种有明确前提的 Local Storage Fast Path,而不是“DRAM 可以退出推理系统”的证明。部署前至少应过下面几道门:
- 先测复用价值。 用真实 Trace 比较 $T_{lookup}+T_{restore}+T_{suffix}$ 与 $T_{\mathrm{full\ prefill}}$,同时看 TTFT、ITL 和 P99,而不只看 Cache Hit Rate。
- 确认 I/O 能直达。 检查 GPU—NVMe PCIe Root Complex、P2P DMA、企业 SSD 的 SGL 支持、队列数与实际 Random I/O;论文数字来自 H100 和高端 PCIe 5.0 NVMe。
- 控制 Profile 漂移。 Model Revision、CUDA/vLLM Kernel、TP 拓扑、SM Partition 或 SSD 组合变化后重新生成 Slack Table。
- 把 Remote Path 单独验收。 本地命中和远端命中分别统计 Bytes、Host Staging、RDMA 与 Tail Latency,不能用 Figure 8 替远端路径背书。
- 补齐持久化成本。 评估 SSD Endurance、Write Amplification、Crash Recovery、Replica Durability 和 Metadata Rebuild;arXiv v1 没有给出这些生产指标。
回到最初的问题
SSD-backed KV Cache 过去不实用,并不只是 SSD 比 DRAM 慢。更致命的是:Paged KV 把长前缀变成大量细碎对象,CPU 又站在每个 I/O 的控制路径上;GPU 算得越快,这段老路径就越刺眼。
Tutti 的回答是把对象、地址、队列和计算窗口一起设计:CPU 做它擅长的索引与准备,GPU 做它擅长的大规模并发发起,Scheduler 决定哪些 I/O 真能藏进推理空隙。论文实验有力支持,本地 H100—NVMe 配置下,这条路可以让 SSD 接近 DRAM-backed LMCache 的端到端表现。
还没有被证明的,是同样收益能自然延伸到任意 GPU、消费级 SSD、大模型、多租户干扰和跨节点 Remote KV。对工程决策而言,这个边界不是给 Tutti 泼冷水,反而让它最有价值的启示更清楚:不要只把 KV Cache 当成要搬运的 Tensor;要把它当成与 GPU 执行共同调度的存储对象。
参考资料
- Shi Qiu et al., Tutti: Making SSD-Backed KV Cache Practical for Long-Context LLM Serving, arXiv:2605.03375v1, 2026-05-05。本文的系统机制、实验数字与两张原图均来自该版本。
- NVIDIA, GPUDirect Storage Overview Guide。用于区分 GDS 的 Direct Data Path 与 CPU Driver Control Path。
- NVIDIA, CUDA Programming Guide: Green Contexts。用于核对 SM Resource Partition 的能力与并发边界。
- Shi Qiu et al., GeminiFS: A Companion File System for GPUs, FAST 2025。Tutti GPU File 与 NVMe File Path 的底层文件系统基础。
- Woosuk Kwon et al., Efficient Memory Management for Large Language Model Serving with PagedAttention, SOSP 2023。Paged KV Block Layout 的基础工作。